دليل شامل لتطبيق خوارزميات أقصر مسار باستخدام بايثون، يغطي خوارزمية دايجسترا، وخوارزمية بيلمان-فورد، والبحث A*. استكشف أمثلة عملية ومقتطفات التعليمات البرمجية.
خوارزميات الرسم البياني في بايثون: تطبيق حلول أقصر مسار
الرسوم البيانية هي هياكل بيانات أساسية في علوم الكمبيوتر، وتستخدم لنمذجة العلاقات بين الكائنات. يعد إيجاد أقصر مسار بين نقطتين في الرسم البياني مشكلة شائعة مع تطبيقات تتراوح من نظام تحديد المواقع العالمي (GPS) إلى توجيه الشبكة وتخصيص الموارد. بايثون، بمكتباتها الغنية وبنيتها الواضحة، هي لغة ممتازة لتطبيق خوارزميات الرسم البياني. يستكشف هذا الدليل الشامل العديد من خوارزميات أقصر مسار وتطبيقاتها في بايثون.
فهم الرسوم البيانية
قبل الغوص في الخوارزميات، دعنا نحدد ماهية الرسم البياني:
- العقد (الرؤوس): تمثل الكائنات أو الكيانات.
- الحواف: تربط العقد، وتمثل العلاقات بينها. يمكن أن تكون الحواف موجهة (في اتجاه واحد) أو غير موجهة (في اتجاهين).
- الأوزان: يمكن أن يكون للحواف أوزان تمثل التكلفة أو المسافة أو أي مقياس آخر ذي صلة. إذا لم يتم تحديد وزن، فغالبًا ما يُفترض أنه 1.
يمكن تمثيل الرسوم البيانية في بايثون باستخدام هياكل بيانات مختلفة، مثل قوائم التجاور ومصفوفات التجاور. سنستخدم قائمة تجاور لأمثلتنا، لأنها غالبًا ما تكون أكثر كفاءة للرسوم البيانية المتفرقة (الرسوم البيانية ذات الحواف القليلة نسبيًا).
مثال على تمثيل الرسم البياني كقائمة تجاور في بايثون:
graph = {
'A': [('B', 5), ('C', 2)],
'B': [('D', 4)],
'C': [('B', 8), ('D', 7)],
'D': [('E', 6)],
'E': []
}
في هذا المثال، يحتوي الرسم البياني على العقد A و B و C و D و E. القيمة المرتبطة بكل عقدة هي قائمة من الصفوف، حيث يمثل كل صف حافة إلى عقدة أخرى ووزن تلك الحافة.
خوارزمية دايجسترا
مقدمة
خوارزمية دايجسترا هي خوارزمية كلاسيكية لإيجاد أقصر مسار من عقدة مصدر واحدة إلى جميع العقد الأخرى في الرسم البياني بأوزان حافة غير سالبة. إنها خوارزمية جشعة تستكشف الرسم البياني بشكل متكرر، وتختار دائمًا العقدة بأصغر مسافة معروفة من المصدر.
خطوات الخوارزمية
- قم بتهيئة قاموس لتخزين أقصر مسافة من المصدر إلى كل عقدة. اضبط المسافة إلى عقدة المصدر على 0 والمسافة إلى جميع العقد الأخرى إلى ما لا نهاية.
- قم بتهيئة مجموعة من العقد التي تمت زيارتها لتكون فارغة.
- بينما توجد عقد لم تتم زيارتها:
- حدد العقدة التي لم تتم زيارتها والتي لها أصغر مسافة معروفة من المصدر.
- ضع علامة على العقدة المحددة على أنها تمت زيارتها.
- لكل جار للعقدة المحددة:
- احسب المسافة من المصدر إلى الجار من خلال العقدة المحددة.
- إذا كانت هذه المسافة أقصر من المسافة المعروفة الحالية إلى الجار، فقم بتحديث مسافة الجار.
- أصبحت أقصر المسافات من المصدر إلى جميع العقد الأخرى معروفة الآن.
تطبيق بايثون
import heapq
def dijkstra(graph, start):
distances = {node: float('inf') for node in graph}
distances[start] = 0
priority_queue = [(0, start)] # (distance, node)
while priority_queue:
distance, node = heapq.heappop(priority_queue)
if distance > distances[node]:
continue # Already processed a shorter path to this node
for neighbor, weight in graph[node]:
new_distance = distance + weight
if new_distance < distances[neighbor]:
distances[neighbor] = new_distance
heapq.heappush(priority_queue, (new_distance, neighbor))
return distances
# Example usage:
graph = {
'A': [('B', 5), ('C', 2)],
'B': [('D', 4)],
'C': [('B', 8), ('D', 7)],
'D': [('E', 6)],
'E': []
}
start_node = 'A'
shortest_distances = dijkstra(graph, start_node)
print(f"Shortest distances from {start_node}: {shortest_distances}")
شرح المثال
يستخدم الكود قائمة انتظار ذات أولوية (تم تنفيذها باستخدام `heapq`) لتحديد العقدة التي لم تتم زيارتها بكفاءة بأصغر مسافة. يخزن قاموس `distances` أقصر مسافة من عقدة البداية إلى كل عقدة أخرى. تقوم الخوارزمية بتحديث هذه المسافات بشكل متكرر حتى تتم زيارة جميع العقد (أو لا يمكن الوصول إليها).
تحليل التعقيد
- التعقيد الزمني: O((V + E) log V)، حيث V هو عدد الرؤوس و E هو عدد الحواف. يأتي عامل log V من عمليات الكومة.
- التعقيد المكاني: O(V)، لتخزين المسافات وقائمة الانتظار ذات الأولوية.
خوارزمية بيلمان-فورد
مقدمة
خوارزمية بيلمان-فورد هي خوارزمية أخرى لإيجاد أقصر مسار من عقدة مصدر واحدة إلى جميع العقد الأخرى في الرسم البياني. على عكس خوارزمية دايجسترا، يمكنها التعامل مع الرسوم البيانية ذات أوزان الحواف السالبة. ومع ذلك، لا يمكنها التعامل مع الرسوم البيانية ذات الدورات السلبية (الدورات التي يكون فيها مجموع أوزان الحافة سالبًا)، لأن هذا سيؤدي إلى أطوال مسار متناقصة بلا حدود.
خطوات الخوارزمية
- قم بتهيئة قاموس لتخزين أقصر مسافة من المصدر إلى كل عقدة. اضبط المسافة إلى عقدة المصدر على 0 والمسافة إلى جميع العقد الأخرى إلى ما لا نهاية.
- كرر الخطوات التالية V-1 مرة، حيث V هو عدد الرؤوس:
- لكل حافة (u, v) في الرسم البياني:
- إذا كانت المسافة إلى u بالإضافة إلى وزن الحافة (u, v) أقل من المسافة الحالية إلى v، فقم بتحديث المسافة إلى v.
- لكل حافة (u, v) في الرسم البياني:
- بعد V-1 تكرارًا، تحقق من وجود دورات سلبية. لكل حافة (u, v) في الرسم البياني:
- إذا كانت المسافة إلى u بالإضافة إلى وزن الحافة (u, v) أقل من المسافة الحالية إلى v، فهناك دورة سلبية.
- إذا تم اكتشاف دورة سلبية، تنتهي الخوارزمية وتُبلغ عن وجودها. وإلا، فإن أقصر المسافات من المصدر إلى جميع العقد الأخرى معروفة.
تطبيق بايثون
def bellman_ford(graph, start):
distances = {node: float('inf') for node in graph}
distances[start] = 0
# Relax edges repeatedly
for _ in range(len(graph) - 1):
for node in graph:
for neighbor, weight in graph[node]:
if distances[node] != float('inf') and distances[node] + weight < distances[neighbor]:
distances[neighbor] = distances[node] + weight
# Check for negative cycles
for node in graph:
for neighbor, weight in graph[node]:
if distances[node] != float('inf') and distances[node] + weight < distances[neighbor]:
return "Negative cycle detected"
return distances
# Example usage:
graph = {
'A': [('B', -1), ('C', 4)],
'B': [('C', 3), ('D', 2), ('E', 2)],
'C': [],
'D': [('B', 1), ('C', 5)],
'E': [('D', -3)]
}
start_node = 'A'
shortest_distances = bellman_ford(graph, start_node)
print(f"Shortest distances from {start_node}: {shortest_distances}")
شرح المثال
يتكرر الكود عبر جميع الحواف في الرسم البياني V-1 مرة، ويقوم بتخفيفها (تحديث المسافات) إذا تم العثور على مسار أقصر. بعد V-1 تكرارًا، يتحقق من وجود دورات سلبية عن طريق التكرار عبر الحواف مرة أخرى. إذا كان لا يزال من الممكن تقليل أي مسافات، فهذا يشير إلى وجود دورة سلبية.
تحليل التعقيد
- التعقيد الزمني: O(V * E)، حيث V هو عدد الرؤوس و E هو عدد الحواف.
- التعقيد المكاني: O(V)، لتخزين المسافات.
خوارزمية البحث A*
مقدمة
خوارزمية البحث A* هي خوارزمية بحث مستنيرة تستخدم على نطاق واسع لإيجاد المسار واجتياز الرسم البياني. فهو يجمع بين عناصر خوارزمية دايجسترا والبحث الاستدلالي لإيجاد أقصر مسار بكفاءة من عقدة بداية إلى عقدة هدف. يعتبر A* مفيدًا بشكل خاص في المواقف التي لديك فيها بعض المعرفة حول مجال المشكلة التي يمكن استخدامها لتوجيه البحث.
الدالة الاستدلالية
المفتاح في البحث A* هو استخدام دالة استدلالية، يُشار إليها بـ h(n)، والتي تقدر تكلفة الوصول إلى عقدة الهدف من عقدة معينة n. يجب أن يكون الاستدلال مقبولاً، مما يعني أنه لا يبالغ أبدًا في تقدير التكلفة الفعلية. تتضمن الاستدلالات الشائعة مسافة إقليدس (المسافة المستقيمة) أو مسافة مانهاتن (مجموع الاختلافات المطلقة في الإحداثيات).
خطوات الخوارزمية
- قم بتهيئة مجموعة مفتوحة تحتوي على عقدة البداية.
- قم بتهيئة مجموعة مغلقة لتكون فارغة.
- قم بتهيئة قاموس لتخزين التكلفة من عقدة البداية إلى كل عقدة (g(n)). اضبط التكلفة على عقدة البداية على 0 والتكلفة على جميع العقد الأخرى إلى ما لا نهاية.
- قم بتهيئة قاموس لتخزين التكلفة الإجمالية المقدرة من عقدة البداية إلى عقدة الهدف من خلال كل عقدة (f(n) = g(n) + h(n)).
- بينما المجموعة المفتوحة ليست فارغة:
- حدد العقدة في المجموعة المفتوحة بأقل قيمة f(n) (العقدة الواعدة ביותר).
- إذا كانت العقدة المحددة هي عقدة الهدف، فأعد بناء المسار وأرجعه.
- انقل العقدة المحددة من المجموعة المفتوحة إلى المجموعة المغلقة.
- لكل جار للعقدة المحددة:
- إذا كان الجار في المجموعة المغلقة، فتخطه.
- احسب تكلفة الوصول إلى الجار من عقدة البداية من خلال العقدة المحددة.
- إذا لم يكن الجار في المجموعة المفتوحة أو كانت التكلفة الجديدة أقل من التكلفة الحالية للجار:
- قم بتحديث التكلفة للجار (g(n)).
- قم بتحديث التكلفة الإجمالية المقدرة للهدف من خلال الجار (f(n)).
- إذا لم يكن الجار في المجموعة المفتوحة، فأضفه إلى المجموعة المفتوحة.
- إذا أصبحت المجموعة المفتوحة فارغة ولم يتم الوصول إلى عقدة الهدف، فلا يوجد مسار من عقدة البداية إلى عقدة الهدف.
تطبيق بايثون
import heapq
def a_star(graph, start, goal, heuristic):
open_set = [(0, start)] # (f_score, node)
closed_set = set()
g_score = {node: float('inf') for node in graph}
g_score[start] = 0
f_score = {node: float('inf') for node in graph}
f_score[start] = heuristic(start, goal)
came_from = {}
while open_set:
f, current_node = heapq.heappop(open_set)
if current_node == goal:
return reconstruct_path(came_from, current_node)
closed_set.add(current_node)
for neighbor, weight in graph[current_node]:
if neighbor in closed_set:
continue
tentative_g_score = g_score[current_node] + weight
if tentative_g_score < g_score[neighbor]:
came_from[neighbor] = current_node
g_score[neighbor] = tentative_g_score
f_score[neighbor] = tentative_g_score + heuristic(neighbor, goal)
if (f_score[neighbor], neighbor) not in open_set:
heapq.heappush(open_set, (f_score[neighbor], neighbor))
return None # No path found
def reconstruct_path(came_from, current_node):
path = [current_node]
while current_node in came_from:
current_node = came_from[current_node]
path.append(current_node)
path.reverse()
return path
# Example Heuristic (Euclidean distance for demonstration, graph nodes should have x, y coords)
def euclidean_distance(node1, node2):
# This example requires the graph to store coordinates with each node, such as:
# graph = {
# 'A': [('B', 5), ('C', 2)],
# 'B': [('D', 4)],
# 'C': [('B', 8), ('D', 7)],
# 'D': [('E', 6)],
# 'E': [],
# 'coords': {
# 'A': (0, 0),
# 'B': (3, 4),
# 'C': (1, 1),
# 'D': (5, 2),
# 'E': (7, 0)
# }
# }
#
# Since we don't have coordinates in the default graph, we'll just return 0 (admissible)
return 0
# Replace this with your actual distance calculation if nodes have coordinates:
# x1, y1 = graph['coords'][node1]
# x2, y2 = graph['coords'][node2]
# return ((x1 - x2)**2 + (y1 - y2)**2)**0.5
# Example Usage:
graph = {
'A': [('B', 5), ('C', 2)],
'B': [('D', 4)],
'C': [('B', 8), ('D', 7)],
'D': [('E', 6)],
'E': []
}
start_node = 'A'
goal_node = 'E'
path = a_star(graph, start_node, goal_node, euclidean_distance)
if path:
print(f"Shortest path from {start_node} to {goal_node}: {path}")
else:
print(f"No path found from {start_node} to {goal_node}")
شرح المثال
تستخدم خوارزمية A* قائمة انتظار ذات أولوية (`open_set`) لتتبع العقد التي سيتم استكشافها، مع إعطاء الأولوية لتلك التي لديها أقل تكلفة إجمالية مقدرة (f_score). يخزن قاموس `g_score` التكلفة من عقدة البداية إلى كل عقدة، ويخزن قاموس `f_score` التكلفة الإجمالية المقدرة للهدف من خلال كل عقدة. يتم استخدام قاموس `came_from` لإعادة بناء أقصر مسار بمجرد الوصول إلى عقدة الهدف.
تحليل التعقيد
- التعقيد الزمني: يعتمد التعقيد الزمني للبحث A* بشكل كبير على الدالة الاستدلالية. في أفضل الحالات، مع وجود استدلال مثالي، يمكن لـ A* إيجاد أقصر مسار في وقت O(V + E). في أسوأ الحالات، مع وجود استدلال ضعيف، يمكن أن يتدهور إلى خوارزمية دايجسترا، مع تعقيد زمني قدره O((V + E) log V).
- التعقيد المكاني: O(V)، لتخزين المجموعة المفتوحة والمجموعة المغلقة و g_score و f_score وقواميس came_from.
اعتبارات عملية وتحسينات
- اختيار الخوارزمية الصحيحة: تعتبر خوارزمية دايجسترا بشكل عام هي الأسرع للرسوم البيانية ذات أوزان الحافة غير السلبية. تعتبر بيلمان-فورد ضرورية عند وجود أوزان حافة سلبية، لكنها أبطأ. يمكن أن يكون البحث A* أسرع بكثير من دايجسترا إذا كان الاستدلال الجيد متاحًا.
- هياكل البيانات: يمكن أن يؤدي استخدام هياكل بيانات فعالة مثل قوائم الانتظار ذات الأولوية (الأكوام) إلى تحسين الأداء بشكل كبير، خاصةً للرسوم البيانية الكبيرة.
- تمثيل الرسم البياني: يمكن أن يؤثر اختيار تمثيل الرسم البياني (قائمة التجاور مقابل مصفوفة التجاور) أيضًا على الأداء. غالبًا ما تكون قوائم التجاور أكثر كفاءة للرسوم البيانية المتفرقة.
- تصميم الاستدلال (لـ A*): تعد جودة الدالة الاستدلالية أمرًا بالغ الأهمية لأداء A*. يجب أن يكون الاستدلال الجيد مقبولاً (لا يبالغ أبدًا في التقدير) ودقيقًا قدر الإمكان.
- استخدام الذاكرة: بالنسبة للرسوم البيانية الكبيرة جدًا، يمكن أن يصبح استخدام الذاكرة مصدر قلق. يمكن أن تساعد تقنيات مثل استخدام المكررات أو المولدات لمعالجة الرسم البياني على شكل أجزاء في تقليل مساحة الذاكرة.
تطبيقات العالم الحقيقي
تتمتع خوارزميات أقصر مسار بمجموعة واسعة من تطبيقات العالم الحقيقي:
- الملاحة عبر نظام تحديد المواقع العالمي (GPS): إيجاد أقصر طريق بين موقعين، مع مراعاة عوامل مثل المسافة وحركة المرور وإغلاق الطرق. تعتمد شركات مثل خرائط جوجل و Waze بشكل كبير على هذه الخوارزميات. على سبيل المثال، إيجاد أسرع طريق من لندن إلى إدنبرة، أو من طوكيو إلى أوساكا بالسيارة.
- توجيه الشبكة: تحديد المسار الأمثل لحزم البيانات للانتقال عبر الشبكة. يستخدم مزودو خدمة الإنترنت خوارزميات أقصر مسار لتوجيه حركة المرور بكفاءة.
- إدارة الخدمات اللوجستية وسلسلة التوريد: تحسين طرق التسليم للشاحنات أو الطائرات، مع مراعاة عوامل مثل المسافة والتكلفة والقيود الزمنية. تستخدم شركات مثل FedEx و UPS هذه الخوارزميات لتحسين الكفاءة. على سبيل المثال، تخطيط طريق الشحن الأكثر فعالية من حيث التكلفة للبضائع من مستودع في ألمانيا إلى العملاء في مختلف البلدان الأوروبية.
- تخصيص الموارد: تخصيص الموارد (مثل النطاق الترددي أو قوة الحوسبة) للمستخدمين أو المهام بطريقة تقلل التكلفة أو تزيد الكفاءة إلى أقصى حد. يستخدم موفرو الحوسبة السحابية هذه الخوارزميات لإدارة الموارد.
- تطوير الألعاب: إيجاد المسار لشخصيات في ألعاب الفيديو. يستخدم البحث A* بشكل شائع لهذا الغرض نظرًا لكفاءته وقدرته على التعامل مع البيئات المعقدة.
- الشبكات الاجتماعية: إيجاد أقصر مسار بين مستخدمين في شبكة اجتماعية، مما يمثل درجة الفصل بينهما. على سبيل المثال، حساب "ست درجات من الفصل" بين أي شخصين على Facebook أو LinkedIn.
مواضيع متقدمة
- البحث ثنائي الاتجاه: البحث من كل من عقدتي البداية والهدف في وقت واحد، والالتقاء في المنتصف. يمكن أن يقلل هذا بشكل كبير من مساحة البحث.
- تسلسل الانكماش: تقنية معالجة مسبقة تنشئ تسلسلًا هرميًا من العقد والحواف، مما يسمح باستعلامات أقصر مسار سريعة جدًا.
- ALT (A*، المعالم، عدم المساواة المثلثية): مجموعة من الخوارزميات القائمة على A* والتي تستخدم المعالم وعدم المساواة المثلثية لتحسين تقدير الاستدلال.
- خوارزميات أقصر مسار متوازية: استخدام معالجات أو سلاسل رسائل متعددة لتسريع حسابات أقصر مسار، خاصةً للرسوم البيانية الكبيرة جدًا.
استنتاج
خوارزميات أقصر مسار هي أدوات قوية لحل مجموعة واسعة من المشكلات في علوم الكمبيوتر وخارجها. توفر بايثون، بتنوعها ومكتباتها الواسعة، منصة ممتازة لتطبيق هذه الخوارزميات والتجربة بها. من خلال فهم المبادئ الكامنة وراء دايجسترا وبيلمان-فورد والبحث A*، يمكنك حل مشكلات العالم الحقيقي التي تنطوي على إيجاد المسار والتوجيه والتحسين بشكل فعال.
تذكر اختيار الخوارزمية التي تناسب احتياجاتك على أفضل وجه بناءً على خصائص الرسم البياني الخاص بك (مثل أوزان الحافة والحجم والكثافة) وتوافر المعلومات الاستدلالية. جرب هياكل البيانات المختلفة وتقنيات التحسين لتحسين الأداء. مع الفهم القوي لهذه المفاهيم، ستكون مجهزًا جيدًا لمواجهة مجموعة متنوعة من تحديات أقصر مسار.